package com.pai4j.pgc.service.es;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.pai4j.common.enums.community.ResourceTypeEnum;
import com.pai4j.common.util.HtmlUtil;
import com.pai4j.common.util.JsonUtil;
import com.pai4j.pgc.pojo.es.DtoRequest;
import com.pai4j.pgc.pojo.es.EsSearchPageResponse;
import com.pai4j.pgc.pojo.es.EsSearchRequest;
import com.pai4j.pgc.pojo.es.EsSearchResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * ES 服务类，提供ES服务的操作接口
 *
 * @Author: yefazhi
 * @Create: 2021/8/28
 * @Version: 1.0
 */
@Service
@Slf4j
public class SearchService {

    // 注入ES高阶客户端
    @Autowired
    private RestHighLevelClient client;

    /**
     * ES当前没有开发和测试环境的数据，commonEsIndex就当作测试和开发环境使用
     */
    @Value("${common-es-index}")
    private String esIndex;

    /**
     * 创建索引
     * @return
     */
    public boolean createIndex() throws IOException {
        // 索引配置
        Settings.Builder settings = Settings.builder()
                .put("number_of_shards", 1)
                .put("number_of_replicas", 1);
        // mappings配置
        XContentBuilder mappings = JsonXContent.contentBuilder()
                .startObject()
                .startObject("properties")
                .startObject("biz_id")
                .field("type", "text")
                .endObject()
                .startObject("description")
                .field("type", "text")
                .endObject()
                .startObject("content")
                .field("type", "text")
                .endObject()
                .startObject("author")
                .field("type", "text")
                .endObject()
                .startObject("click_count")
                .field("type", "integer")
                .endObject()
                .startObject("source_type")
                .field("type", "text")
                .endObject()
                .startObject("create_date")
                .field("type", "date")
                .field("format", "yyyy-MM-dd")
                .endObject()
                .startObject("update_date")
                .field("type", "date")
                .field("format", "yyyy-MM-dd")
                .endObject()
                .endObject()
                .endObject();
        // 创建索引
        return this.createIndex(esIndex, settings, mappings);
    }

    /***
     * 创建索引
     * @param indexName
     * @param settings
     * @param source
     * @return
     * @throws IOException
     */
    public boolean createIndex(String indexName, Settings.Builder settings, XContentBuilder source) throws IOException {
        CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName).settings(settings).mapping(source);
        return client.indices().create(createIndexRequest, RequestOptions.DEFAULT).isAcknowledged();
    }

    /**
     * 判断索引是否存在
     * @param indexName
     * @return
     * @throws IOException
     */
    public boolean existIndex(String indexName) throws IOException {
        return client.indices().exists(new GetIndexRequest(indexName), RequestOptions.DEFAULT);
    }

    /**
     * 删除索引
     * @return
     * @throws IOException
     */
    public boolean deleteIndex() throws IOException {
        return deleteIndex(esIndex);
    }

    public boolean deleteIndex(String indexName) throws IOException {
        return client.indices().delete(new DeleteIndexRequest(indexName), RequestOptions.DEFAULT).isAcknowledged();
    }

    /***
     * 保存文档
     * @param index
     * @param source
     */
    public void saveDoc(String index, DtoRequest source) {
        String id = buildDocId(source.getSourceType(), source.getId());
        IndexRequest request = new IndexRequest(index, "_doc", id);
        request.source(JsonUtil.toJsonString(source), XContentType.JSON);
        try {
            client.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("ES 文档创建异常！ID:{},", id, e);
        }
    }

    /***
     * 删除文档
     * @param index
     */
    public void deleteDoc(String index, String id) {
        DeleteRequest request = new DeleteRequest(index, "_doc", id);
        try {
            client.delete(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("ES 文档删除异常！ID:{},", id, e);
        }
    }

    /***
     * 批量创建文章
     * @param sourceList
     */
    public void bulkSaveDoc(List<DtoRequest> sourceList) {
        BulkRequest bulkRequest = new BulkRequest();
        Lists.partition(sourceList, 2).forEach(list -> list.forEach(source -> {
            String id = buildDocId(source.getSourceType(), source.getId());
            bulkRequest.add(new IndexRequest(esIndex).id(id).source(JsonUtil.toJsonString(source), XContentType.JSON));
        }));
        try {
            client.bulk(bulkRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("ES 文档批量创建异常！", e);
        }
    }

    /***
     * 批量删除
     * @param sourceList
     */
    public void bulkDeleteDoc(List<DtoRequest> sourceList) {
        BulkRequest bulkRequest = new BulkRequest();
        sourceList.forEach(source -> {
            String id = buildDocId(source.getSourceType(), source.getId());
            bulkRequest.add(new DeleteRequest(esIndex, "_doc", id));
        });
        try {
            client.bulk(bulkRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("ES 文档批量创建异常！", e);
        }
    }

    /***
     * 搜索
     * @return
     */
    public EsSearchPageResponse suggest(EsSearchRequest searchRequest) {
        //设置分页参数
        Integer pageNo = searchRequest.getPageNo() - 1;
        Integer pageSize = searchRequest.getPageSize();
        // 1. 创建request
        SearchRequest request = new SearchRequest(esIndex);
        // 2. 指定查询条件
        SearchSourceBuilder builder = new SearchSourceBuilder();

        builder.timeout(new TimeValue(60, TimeUnit.SECONDS));

        HighlightBuilder highlightBuilder = new HighlightBuilder();
        //设置高亮
        highlightBuilder.requireFieldMatch(false);//如果要多个字段高亮,这项要为false
        builder.highlighter(highlightBuilder);

        String t = searchRequest.getType();
        String kw = searchRequest.getKeyword();
        if (StringUtils.isBlank(t) && StringUtils.isBlank(kw)) {
            builder.query(QueryBuilders.matchAllQuery());
            builder.sort("create_date", SortOrder.DESC);
        } else if (StringUtils.isBlank(t) && StringUtils.isNotBlank(kw)) {
            BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery()
                    .should(QueryBuilders.matchQuery("title", kw))
                    .should(QueryBuilders.matchQuery("content", kw));
            builder.query(queryBuilder);
            builder.sort("_score", SortOrder.DESC);
        } else if (StringUtils.isNotBlank(t) && StringUtils.isBlank(kw)) {
            builder.query(QueryBuilders.matchQuery("source_type", t));
            builder.sort("create_date", SortOrder.DESC);
        } else if (StringUtils.isNotBlank(t) && StringUtils.isNotBlank(kw)) {
            BoolQueryBuilder queryBuilder1 =
                    QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("source_type", t));
            BoolQueryBuilder queryBuilder2 = QueryBuilders.boolQuery()
                    .should(QueryBuilders.matchQuery("title", kw))
                    .should(QueryBuilders.matchQuery("content", kw));
            BoolQueryBuilder queryBuilder =
                    QueryBuilders.boolQuery().must(queryBuilder1).must(queryBuilder2);
            builder.query(queryBuilder);
            builder.sort("_score", SortOrder.DESC);
        }
        builder.size(pageSize);
        builder.from(pageNo * pageSize);
        request.source(builder);
        // 3. 执行查询
        SearchResponse response = null;
        // 4. 处理查询结果
        List<EsSearchResponse> searchList = new ArrayList<>();
        try {
            response = client.search(request, RequestOptions.DEFAULT);
            for (SearchHit hit : response.getHits()) {
                Map<String, Object> searchRespMap = hit.getSourceAsMap();
                EsSearchResponse esSearchResponse = new EsSearchResponse();
                esSearchResponse.setSourceType(searchRespMap.get("source_type").toString());
                esSearchResponse.setClickCount(searchRespMap.get("click_count") == null ||
                        "null".equals(String.valueOf(searchRespMap.get("click_count"))) ? 0L
                        : Long.parseLong(String.valueOf(searchRespMap.get("click_count"))));
                esSearchResponse.setId(String.valueOf(searchRespMap.get("id")));
                Map<String, Object> dataMap = JSONObject.parseObject(searchRespMap.get("data").toString());
                if (ResourceTypeEnum.BBS.getType().equals(esSearchResponse.getSourceType())) {
                    dataMap.put("content", HtmlUtil.filterHtml(String.valueOf(dataMap.get("content"))));
                }
                esSearchResponse.setAllData(dataMap);
                searchList.add(esSearchResponse);
            }
            long total = response.getHits().getTotalHits().value;
            long modeValue = total % pageSize;
            long totalPage = 0L == modeValue ? total / pageSize : (total / pageSize) + 1;
            return EsSearchPageResponse.of(searchRequest.getPageNo(), pageSize, totalPage, total, searchList);
        } catch (IOException e) {
            log.error("ES 查询失败! request:{}", JsonUtil.toJsonString(searchRequest), e);
        }
        return EsSearchPageResponse.of(searchRequest.getPageNo(), pageSize, 1L, 0L, searchList);
    }

    private String buildDocId(String sourceType, String bizId) {
        return sourceType.concat("_").concat(bizId);
    }
}
